#include "guish.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/termios.h>
#include <fcntl.h>
#include <pty.h>
#include <errno.h>
#include <poll.h>

#ifdef __CYGWIN__
// Cygwin doesn't currently have posix_openpt(3). This simple replacement
// was suggested by Elliott Hughes on the Cygwin mailing list.
int posix_openpt(int oflag) {
	return open("/dev/ptmx", oflag);
}
#endif

PseudoTerminal::PseudoTerminal(int width, int height)
{
	char ptySlaveName[256];
	openpty(&masterFd, &slaveFd, ptySlaveName, NULL, NULL);
	
	slaveName = ptySlaveName;
	
	setSize(width, height);
	
	setFileAsync(masterFd, true);
	
	makeTermRaw(masterFd);
	
	disabled = false;
}

PseudoTerminal::~PseudoTerminal()
{
	if(masterFd >= 0)
		close(masterFd);
	if(slaveFd >= 0)
		close(slaveFd);
}

void PseudoTerminal::sendInput(const char *data, int len)
{
	pendingInput.append(len, data);
	pushInput();
}

bool PseudoTerminal::hasPendingInput()
{
	return pendingInput.getLen() > 0;
}

void PseudoTerminal::pushInput()
{
	int bytesWritten = write(masterFd, pendingInput.getData(), pendingInput.getLen());
	if(bytesWritten > 0) {
		pendingInput.popFront(bytesWritten);
	}
}

void PseudoTerminal::setSize(int width, int height)
{
	// For some reason, this is crashing on Cygwin. For now, just stub it and
	// have only 80x25 terminals.
	struct winsize size;
	memset(&size, 0, sizeof(winsize));
	size.ws_row = height;
	size.ws_col = width;
	if(ioctl(masterFd, TIOCSWINSZ, &size) < 0)
		trace << "ioctl in setTerminalSize failed!\n";
}

void PseudoTerminal::reEnable()
{
	disabled = false;
}

void PseudoTerminal::setAsLogin()
{
	setsid();
	
	// Open an extra copy of the slave side of the pty, then immediately close
	// it, to set this process's controlling terminal. (Workaround for lack of
	// ioctl(TIOCSCTTY) on some platforms.)
	int extraSlaveFd = open(slaveName.c_str(), O_RDWR);
	close(extraSlaveFd);
	
	if(grantpt(masterFd) < 0) {
		trace << "grantpt failed on fd "<<masterFd<<"!\n";
		return;
	}
	close(masterFd);
	
	dup2(slaveFd, 0);
	dup2(slaveFd, 1);
	dup2(slaveFd, 2);
	close(slaveFd);
}

string PseudoTerminal::getLeftoverInput()
{
	// Read any text back from the slave side of the terminal which wasn't
	// handled by the client process
	
	// Add a newline, to force out pending input in canonical mode
	write(masterFd, "\n", 1);
	
	string ret="";
	char buf[512];
	int bytesRead;
	do {
		struct pollfd pollfd;
		pollfd.fd = slaveFd;
		pollfd.events = POLLIN;
		pollfd.revents = 0;
		
		int pollResult = poll(&pollfd, 1, 0);
		if(pollResult <= 0)
			break;
		
		bytesRead = read(slaveFd, buf, 512);
		if(bytesRead>0)
			ret += string(buf, bytesRead);
		else if(bytesRead<0) {
			trace << "err is "<<strerror(errno)<<"\n"; //DEBUG
		}
	} while(bytesRead > 0);
	
	// Strip off the extra newline
	if(ret.length()>0 && ret[ret.length()-1]=='\n')
		ret = ret.substr(0, ret.length()-1);
	
	// Add any text which wasn't pushed to the terminal
	if(pendingInput.getLen()>0)
		ret += string(pendingInput.getData(), pendingInput.getLen());
	
	return ret;
}

int PseudoTerminal::getMasterFd()
{
	return masterFd;
}

